package io.renren.modules.webapi.service.impl;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;
import io.renren.common.properties.FileConstants;
import io.renren.common.utils.*;
import io.renren.modules.file.entity.FileChunkEntity;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.List;

import io.renren.modules.webapi.dao.InnerFileDao;
import io.renren.modules.webapi.entity.InnerFileEntity;
import io.renren.modules.webapi.service.InnerFileService;


@Service("innerFileService")
public class InnerFileServiceImpl extends ServiceImpl<InnerFileDao, InnerFileEntity> implements InnerFileService {

    @Value("${spring.file.upFinalFolder}")
    private String upFinalFolder;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        String key = (String)params.get("key");

        Page<InnerFileEntity> page = this.selectPage(
                new Query<InnerFileEntity>(params).getPage(),
                new EntityWrapper<InnerFileEntity>().like(StringUtils.isNotBlank(key),"id", key)
        );

        return new PageUtils(page);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<InnerFileEntity> queryList(Map<String, Object> params){
        // 通过父ID 获取列表
        Long parentId = Long.valueOf(params.get("parentId").toString());;
        List<InnerFileEntity> list = this.selectList(
                new EntityWrapper<InnerFileEntity>().eq("parent_id", parentId)
        );
        return list;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public InnerFileEntity queryEntity(Map<String, Object> params){
        String fileName = params.get("fileName").toString();
        InnerFileEntity entity = this.selectOne(new EntityWrapper<InnerFileEntity>().eq("file_name",fileName));
        return entity;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public InnerFileEntity save(Map<String, Object> params) {
        String fileName = params.get("fileName").toString();
        String userName = params.get("userName").toString();
        Long parentId = Long.valueOf(params.get("parentId").toString());

        Long maxID = baseMapper.getMaxId();
        maxID = maxID == null ? 1 : maxID + 1;
        InnerFileEntity entity = new InnerFileEntity();
        entity.setId(maxID);
        entity.setCreateTime(new Date());
        entity.setDir(Boolean.valueOf(params.get("dir").toString()));
        entity.setOperation(entity.getDir()? FileConstants.SAVEDIRMODE : FileConstants.SAVEMODE);
        entity.setCreateUsername(userName);
        entity.setFileName(fileName);
        entity.setParentId(parentId);
        entity.setUpdateFlag(true);
        this.insert(entity);

        this.createDir(entity.getId(),"");
        return entity;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(InnerFileEntity entity) {
        entity.setModifyTime(new Date());
        entity.setUpdateFlag(true);
        this.updateById(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(InnerFileEntity entity){
        entity.setCreateTime(new Date());
        entity.setOperation(FileConstants.SAVEDIRMODE);
        entity.setId(this.baseMapper.getMaxId() + 1);
        this.insert(entity);
        this.createDir(entity.getId(),"");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBatch(Long[] Ids) {
        //删除
        this.deleteBatchIds(Arrays.asList(Ids));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteByID(Long id){
        String parentDir = this.getParentDir(id , "");
        this.deleteById(id);
        this.delete(new EntityWrapper<InnerFileEntity>().eq("parent_id",id));
        FileUtil.deleteDirOrFile(new File(upFinalFolder + parentDir));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createDir(Long id,String path){
        InnerFileEntity entity = this.selectById(id);
        if (entity.getParentId() != 0L) {
            this.createDir(entity.getParentId(),entity.getFileName() + "/" + path);
        } else {
            FileUtil.createDir(upFinalFolder + entity.getFileName() + "/" +path );
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String getParentDir(Long id,String path) {
        InnerFileEntity entity = this.selectById(id);
        if (entity.getParentId() != 0L) {
            return this.getParentDir(entity.getParentId(),entity.getFileName() + "/" + path);
        } else {
            return entity.getFileName() + "/" + path;
        }
    }

    @Override
    public String chunkUploadByteBuffer(FileChunkEntity param, String filePath) {
        try {
            if (StringUtil.isEmpty(param.getTaskId())) {
                param.setTaskId(UuidUtil.getShortUUID());
            }
            /**
             *
             * 1：创建临时文件，和源文件一个路径
             * 2：如果文件路径不存在重新创建
             */
            String fileName = param.getFile().getOriginalFilename();
            String tempFileName = param.getTaskId() + "_tmp";
            File fileDir = new File(filePath);
            if (!fileDir.exists()) {
                fileDir.mkdirs();
            }
            File tempFile = new File(filePath, tempFileName);
            //第一步
            RandomAccessFile raf = new RandomAccessFile(tempFile, "rw");
            //第二步
            FileChannel fileChannel = raf.getChannel();
            //第三步 计算偏移量
            long position = (param.getChunkNumber() - 1) * param.getChunkSize();
            //第四步
            byte[] fileData = param.getFile().getBytes();
            //第五步
            long end = position + fileData.length - 1;
            fileChannel.position(position);
            fileChannel.write(ByteBuffer.wrap(fileData));
            //使用 fileChannel.map的方式速度更快，但是容易产生IO操作，无建议使用
//        MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE,position,fileData.length);
//        //第六步
//        mappedByteBuffer.put(fileData);
            //第七步
//        freedMappedByteBuffer(mappedByteBuffer);
//        Method method = FileChannelImpl.class.getDeclaredMethod("unmap", MappedByteBuffer.class);
//        method.setAccessible(true);
//        method.invoke(FileChannelImpl.class, mappedByteBuffer);
            fileChannel.force(true);
            fileChannel.close();
            raf.close();
            //第八步
            boolean isComplete = checkUploadStatus(param, fileName, filePath);
            if (isComplete) {
                renameFile(tempFile, fileName);
                // 上传成功后 记录文件
                InnerFileEntity fileEntity = new InnerFileEntity();
                fileEntity.setFileName(fileName);
                fileEntity.setParentId(Long.valueOf(param.getRelativePath().toString().split(",")[0]));
                fileEntity.setDir(false);
                fileEntity.setOperation(FileConstants.SAVEMODE);
                fileEntity.setUpdateFlag(true);
                fileEntity.setCreateUsername(param.getRelativePath().toString().split(",")[1]);
                this.save(fileEntity);

            }
            return param.getTaskId();
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * 文件重命名
     * @param toBeRenamed   将要修改名字的文件
     * @param toFileNewName 新的名字
     * @return
     */
    public void renameFile(File toBeRenamed, String toFileNewName) {
        //检查要重命名的文件是否存在，是否是文件
        if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
            System.out.println("文件不存在");
            return;
        }
        String p = toBeRenamed.getParent();
        File newFile = new File(p + File.separatorChar + toFileNewName);
        // 检查上传文件是否已经存在，如果存在则删除原来文件
        if (newFile.exists()) {
            newFile.delete();
        }
        //修改文件名
        toBeRenamed.renameTo(newFile);
    }

    /**
     * 检查文件上传进度
     * @return
     */
    public boolean checkUploadStatus(FileChunkEntity param,String fileName,String filePath) throws IOException {
        File confFile = new File(filePath,fileName+".conf");
        RandomAccessFile confAccessFile = new RandomAccessFile(confFile,"rw");
        //设置文件长度
        confAccessFile.setLength(param.getTotalChunks());
        //设置起始偏移量
        confAccessFile.seek(param.getChunkNumber()-1);
        //将指定的一个字节写入文件中 127，
        confAccessFile.write(Byte.MAX_VALUE);
        byte[] completeStatusList = FileUtils.readFileToByteArray(confFile);
        confAccessFile.close();//不关闭会造成无法占用
        //这一段逻辑有点复杂，看的时候思考了好久，创建conf文件文件长度为总分片数，每上传一个分块即向conf文件中写入一个127，那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE 127
        for(int i = 0; i<completeStatusList.length; i++){
            if(completeStatusList[i]!=Byte.MAX_VALUE){
                return false;
            }
        }
        //如果全部文件上传完成，删除conf文件
        confFile.delete();
        return true;
    }
    /**
     * 在MappedByteBuffer释放后再对它进行读操作的话就会引发jvm crash，在并发情况下很容易发生
     * 正在释放时另一个线程正开始读取，于是crash就发生了。所以为了系统稳定性释放前一般需要检 查是否还有线程在读或写
     * @param mappedByteBuffer
     */
    public static void freedMappedByteBuffer(final MappedByteBuffer mappedByteBuffer) {
        try {
            if (mappedByteBuffer == null) {
                return;
            }
            mappedByteBuffer.force();
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    try {
                        Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner", new Class[0]);
                        //可以访问private的权限
                        getCleanerMethod.setAccessible(true);
                        //在具有指定参数的 方法对象上调用此 方法对象表示的底层方法
                        sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedByteBuffer,
                                new Object[0]);
                        cleaner.clean();
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.out.println("清理缓存出错!!!" + e.getMessage());
                    }
                    System.out.println("缓存清理完毕!!!");
                    return null;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}